业界主流方案、优势、实现方式与代码示例
更新日期:2026-06-17
背景问题
AI 对话中,每次请求都会把完整的历史消息(包括 thinking、工具调用输出、文件内容等)传给模型。随着对话拉长,上下文迅速膨胀,带来三个问题:
- 成本高:按 token 计费,上下文越长越贵
- 速度慢:首 token 延迟随上下文长度线性增加
- 质量下降:”Lost in the Middle” 现象——模型对上下文中间部分关注力最差
1
| Token 消耗 ≈ 系统提示 + 历史对话 × 轮数 + 工具输出 × 调用次数
|
即使模型支持 200K/1M token 窗口,装得下 ≠ 效果好 + 花钱少。
方案一:删除压缩(Compaction)
原理
直接删除低信号内容,保留的原文一字不改。不生成新 token,零幻觉风险。
典型删除对象:
- 工具调用的原始输出(grep 返回的 100 个文件 → 只保留匹配行)
- 文件 diff 的全量内容 → 只保留变更摘要
- 浏览器截屏的冗长 HTML → 只保留关键文本
- 重复的 thinking 过程
优势
| 维度 |
评价 |
| 压缩比 |
50-70% |
| 准确性 |
100%(保留下来的内容 = 原文) |
| 幻觉风险 |
0% |
| 速度 |
快(本地正则/规则即可,无需 LLM) |
业界工具
- Headroom — 开源上下文压缩层,10k+ star。SmartCrusher(JSON) + CodeCompressor(AST) + Kompress-base(文本),压缩率 90%+
- RTK — 压缩 CLI 命令输出(
git show、ls 等)
- lean-ctx — CLI + MCP 工具的上下文精简
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function compactGrepOutput(raw: string): string { const lines = raw.split('\n'); const significant = lines.filter(line => { return line.trim() && !/^─+$/.test(line) && !/^\d+$/.test(line); }); return significant.join('\n'); }
function compactJsonResponse(raw: string, keepFields: string[]): string { const parsed = JSON.parse(raw); if (Array.isArray(parsed)) { return JSON.stringify(parsed.map(item => { const result: Record<string, any> = {}; for (const field of keepFields) { if (field in item) result[field] = item[field]; } return result; })); } return raw; }
|
方案二:摘要压缩(Summarization)
原理
用 LLM 将多轮对话/长文本提炼成一段摘要,替换原始内容。后续对话基于摘要 + 最近的 N 轮完整消息进行。
优势
| 维度 |
评价 |
| 压缩比 |
极高(100 轮对话 → 200 字摘要) |
| 信息密度 |
高(关键点被提炼出来) |
| 适用场景 |
长周期对话、客服、教育辅导 |
劣势
| 维度 |
评价 |
| 幻觉风险 |
有(摘要可能漏掉或歪曲细节) |
| 额外开销 |
每次压缩需要一次 LLM 调用 |
| 反复压缩衰减 |
摘要的摘要会不断丢失信息 |
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class ConversationSummarizer { private summary = ''; private recentMessages: Message[] = []; private readonly threshold = 4000;
addMessage(msg: Message) { this.recentMessages.push(msg); if (this.estimateTokens() > this.threshold) { this.compress(); } }
async compress() { const toCompress = this.summary + '\n' + this.recentMessages.map(m => `${m.role}: ${m.content}`).join('\n');
const prompt = `请将以下对话历史压缩成一段简洁摘要,保留: 1. 用户的关键信息(身份、偏好、已做的决定) 2. 重要的任务状态和上下文 3. 后续对话可能需要的事实
原始对话: ${toCompress}
摘要:`;
this.summary = await llm.chat(prompt); this.recentMessages = []; }
buildContext(): string { if (!this.summary) return this.recentMessages.map(m => `${m.role}: ${m.content}`).join('\n'); return `【历史摘要】\n${this.summary}\n\n【最近对话】\n${ this.recentMessages.map(m => `${m.role}: ${m.content}`).join('\n') }`; }
private estimateTokens(): number { const totalChars = this.summary.length + this.recentMessages.map(m => m.content.length).reduce((a, b) => a + b, 0); return Math.ceil(totalChars / 2); } }
|
业界使用案例
| 产品 |
做法 |
| OpenAI Codex CLI |
调用 compact() API 对历史对话做摘要压缩 |
| Claude Code |
内置 compaction 机制,达到阈值后自动摘要 |
| Anthropic Compaction API |
服务端侧对话历史摘要(需付费) |
方案三:滑动窗口(Sliding Window)
原理
只保留最近 N 轮对话,更早的全部丢弃。实现最简单。
常见策略
| 策略 |
做法 |
| 按轮数截断 |
保留最后 K 条消息 |
| 按 token 数截断 |
从尾部往前数,裁到预算内 |
| 按重要性排序截断 |
结合评分(相关性 + 时效性)丢弃最低分 |
优势
| 维度 |
评价 |
| 实现难度 |
★☆☆☆☆ 极简单 |
| 速度 |
无额外开销 |
| 可预测 |
token 消耗固定 |
劣势
| 维度 |
评价 |
| 信息丢失 |
早期关键信息直接丢失 |
| 长任务中断 |
做了一半的任务,模型”失忆” |
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| function slidingWindow(messages: Message[], maxTokens: number): Message[] { let total = 0; const result: Message[] = [];
for (let i = messages.length - 1; i >= 0; i--) { const tokens = estimateTokens(messages[i].content); if (total + tokens > maxTokens) break; total += tokens; result.unshift(messages[i]); }
if (messages[0]?.role === 'system') { if (result[0]?.role !== 'system') { result.unshift(messages[0]); } }
return result; }
function smartWindow(messages: Message[], maxTokens: number): Message[] { if (estimateTotalTokens(messages) <= maxTokens) return messages;
const systemMsg = messages.filter(m => m.role === 'system'); const history = messages.filter(m => m.role !== 'system');
const tailBudget = Math.floor(maxTokens * 0.3); const headBudget = maxTokens - tailBudget - estimateTotalTokens(systemMsg);
const tail: Message[] = []; let tailTokens = 0; for (let i = history.length - 1; i >= 0; i--) { const t = estimateTokens(history[i].content); if (tailTokens + t > tailBudget) break; tailTokens += t; tail.unshift(history[i]); }
const head: Message[] = []; let headTokens = 0; for (const msg of history) { if (head.includes(msg) || tail.includes(msg)) break; const t = estimateTokens(msg.content); if (headTokens + t > headBudget) break; headTokens += t; head.push(msg); }
return [...systemMsg, ...head, ...tail]; }
|
方案四:向量记忆 / RAG(检索式记忆)
原理
不保存全部历史,而是把消息向量化存入数据库(Chroma、Milvus 等),每次只检索最相关的几条。
优势
| 维度 |
评价 |
| 理论上限 |
近乎无限记忆(只受存储限制) |
| 相关性 |
高(语义检索找到最相关的) |
| Token 消耗 |
极低(只塞相关片段) |
劣势
| 维度 |
评价 |
| 实现复杂度 |
★★★★☆ 高 |
| 额外组件 |
需要向量数据库 + embedding 模型 |
| 检索质量 |
依赖 embedding 质量,可能漏检 |
| 延迟 |
每次多一次向量检索(通常 < 50ms) |
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class VectorMemory { constructor( private embeddingClient: EmbeddingAPI, private vectorDb: VectorDatabase, private topK = 5 ) {}
async addMessage(msg: Message) { const embedding = await this.embeddingClient.embed(msg.content); await this.vectorDb.insert({ id: msg.id, vector: embedding, metadata: { role: msg.role, timestamp: msg.timestamp, content: msg.content } }); }
async retrieve(query: string, limit = 5): Promise<Message[]> { const queryEmbedding = await this.embeddingClient.embed(query); const results = await this.vectorDb.search(queryEmbedding, limit); return results.map(r => ({ role: r.metadata.role, content: r.metadata.content, timestamp: r.metadata.timestamp })); }
async buildContext(query: string, recentMessages: Message[]): Promise<string> { const relevant = await this.retrieve(query, this.topK); const context = [ '【相关历史】', ...relevant.map(m => `${m.role}: ${m.content}`), '', '【最近对话】', ...recentMessages.map(m => `${m.role}: ${m.content}`) ].join('\n'); return context; } }
|
方案五:多代理隔离(Multi-Agent Isolation)
原理
每个子代理维护自己的小上下文,只关注自己的任务。主代理只持有摘要级别的全局上下文。
优势
| 维度 |
评价 |
| 单 agent 上下文 |
小(只关注子任务) |
| 隔离性好 |
一个 agent 的 thinking 不会污染另一个 |
| 可并行 |
多个 agent 可同时工作 |
劣势
| 维度 |
评价 |
| 架构复杂度 |
★★★★★ 很高 |
| 协调开销 |
需要主 agent 做路由和汇总 |
| 信息孤岛 |
子 agent 看不到全局,可能做出次优决策 |
实现示意
1 2 3 4 5 6 7 8 9 10 11
| ┌─────────────────┐ │ Orchestrator │ ← 只持有摘要上下文 │ (主代理) │ └────────┬────────┘ │ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ Frontend │ │ Backend │ │ Database │ ← 每个只关心自己的上下文 │ Agent │ │ Agent │ │ Agent │ └────────────┘ └────────────┘ └────────────┘
|
方案六:上下文缓存(Context Caching)
原理
静态上下文(系统提示、文档、知识库)只在第一次完整传入,后续请求复用缓存结果,只传变化的部分。
提供商支持
| 平台 |
功能 |
效果 |
| Kimi (月之暗面) |
上下文缓存 API |
降本 90%,首 token 延迟降 83% |
| OpenAI |
Prompt Caching |
缓存命中时 50% 折扣 |
| Anthropic |
Prompt Caching |
缓存命中时降价 85-90% |
| Google Gemini |
Context Caching |
长文档场景高效 |
适用场景
- 固定文档的大量提问(产品说明书、法律文档、代码库分析)
- 高流量的 AI 应用(同一份知识库被反复查询)
- Agent 长期任务(系统提示不变,只变用户输入)
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class ContextCache { private cache: Map<string, CachedContext> = new Map();
async getOrCreate(key: string, buildContext: () => Promise<string>): Promise<string> { const cached = this.cache.get(key); if (cached && !this.isExpired(cached)) { return cached.context; }
const context = await buildContext(); this.cache.set(key, { context, createdAt: Date.now(), ttl: 10 * 60 * 1000 }); return context; }
private isExpired(cached: CachedContext): boolean { return Date.now() - cached.createdAt > cached.ttl; } }
|
方案七:Token 预算管理(Token Budgeting)
原理
把上下文窗口当成预算来分配,不同部分有不同的优先级。
常见分配
1 2 3 4 5 6 7
| 总窗口 100K tokens ├── 系统提示: 2K (2%) ← 固定 ├── 对话历史: 10K (10%) ← 滑动窗口 + 摘要 ├── 工具输出: 30K (30%) ← compaction 压缩 ├── 检索文档: 30K (30%) ← 按相关性排序 ├── 当前用户输入: 3K (3%) ← 完整保留 └── 模型回答预留: 25K (25%) ← 回复空间
|
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class TokenBudget { private readonly budget = { system: 0.02, history: 0.10, tools: 0.30, retrieval: 0.30, input: 0.03, output: 0.25 };
constructor(private maxTokens: number) {}
allocate(): Record<string, number> { return Object.fromEntries( Object.entries(this.budget).map(([key, ratio]) => [ key, Math.floor(this.maxTokens * ratio) ]) ); }
enforce(section: string, content: string): string { const limit = this.allocate()[section]; const tokens = estimateTokens(content); if (tokens <= limit) return content;
const target = Math.floor(limit * 0.9); return truncateToTokens(content, target); } }
|
方案八:Thinking 过滤(Thinking Stripping)
原理
在把上下文传给模型之前,剥离 assistant 回复中的 thinking/推理部分,只保留最终回答。
优势
| 维度 |
评价 |
| 信息损失 |
最小(最终回答已包含结论和关键推理) |
| 压缩比 |
因模型而异(thinking 可占回复的 30-70%) |
| 实现难度 |
★☆☆☆☆ 简单 |
实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function stripThinking(messages: Message[]): Message[] { return messages.map(msg => { if (msg.role !== 'assistant') return msg;
const cleaned = msg.content.replace(/[\s\S]*?<\/thinking>/g, ''); return { ...msg, content: cleaned.trim() }; }); }
function stripToolOutput(messages: Message[]): Message[] { return messages.filter(msg => { if (msg.role === 'tool') { return msg.isFinalResult ?? true; } return true; }); }
|
方案对比总结
| 方案 |
压缩比 |
信息损失 |
幻觉风险 |
实现难度 |
额外开销 |
适用场景 |
| ① 删除压缩 |
50-70% |
低 |
0% |
★☆☆☆☆ |
无 |
工具输出、日志、grep结果 |
| ② 摘要压缩 |
90%+ |
中 |
有 |
★★★☆☆ |
LLM调用 |
长对话、客服、辅导 |
| ③ 滑动窗口 |
可控 |
高 |
无 |
★☆☆☆☆ |
无 |
简单对话、短期任务 |
| ④ 向量记忆 |
极高 |
中 |
低 |
★★★★☆ |
向量检索 |
超长期对话、知识库问答 |
| ⑤ 多代理隔离 |
极高 |
低 |
低 |
★★★★★ |
协调开销 |
复杂工程、多步骤任务 |
| ⑥ 上下文缓存 |
依场景 |
0%(静态部分) |
0% |
★★☆☆☆ |
无 |
固定文档高频查询 |
| ⑦ Token预算管理 |
可控 |
可控 |
无 |
★★☆☆☆ |
无 |
生产系统、成本控制 |
| ⑧ Thinking过滤 |
30-70% |
极低 |
0% |
★☆☆☆☆ |
无 |
AI Agent对话、编程助手 |
组合方案示例
实际生产环境不会只用一种方案,而是多层组合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 接收用户输入 │ ▼ ① Token 预算分配 ← 系统提示 2% + 回答预留 25% │ ▼ ② Thinking 过滤 ← 剥离上一轮 assistant 的 │ ▼ ③ 删除压缩 (Compaction) ← 压缩工具输出、JSON 响应、日志 │ ▼ ④ 滑动窗口 + 摘要 ← 最近 20 轮完整 + 更早的历史摘要 │ ▼ ⑤ 向量检索 (RAG) ← 根据当前问题,检索最相关的历史 │ ▼ 组合成最终上下文 → 传给 LLM
|
真实案例:OpenCode 当前可以做的是
1 2 3 4 5 6 7 8 9 10
| 当前状态(你正在用的): ✅ 系统提示 + Behavior Instructions(大几千 token) ❌ Thinking 不过滤(我的 全部进上下文) ❌ 工具输出不做压缩 ❌ 对话不做摘要
可以立即做的优化: 1. 自定义 agent + prompt 规范输出格式 2. 创建 output-format skill 控制回答结构 3. 等待 OpenCode 支持 thinking 剥离(目前不支持)
|
参考资料